package com.lxy.file.server.impl;

import com.alibaba.fastjson.JSONObject;
import com.lxy.file.dao.FileOperationDao;
import com.lxy.file.server.FileOperationService;
import com.lxy.file.util.FileUtil;
import com.lxy.file.util.RedisUtil;
import com.sun.glass.ui.Size;
import org.apache.commons.io.FileUtils;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * @Author lxy
 * @Date 2021/12/28 18:49
 * @Description
 */
@Service
public class FileOperationServiceImpl implements FileOperationService {

    @Autowired
    private FileOperationDao fileOperationDao;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private FileUtil fileUtil;

    @Autowired
    private Redisson redisson;

    /**
     * @author lxy
     * @date 2021-12-28 19:36
     * @param hashcode
     * @param fileTarSize 文件目标长度
     * @return java.lang.String
     * @throws
     * @since
     */
    @Override
    public Integer checkFileStatus(String hashcode, Integer fileTarSize) {
        boolean b = redisUtil.hasKey(hashcode);
        if(b) { //判断redis中是否有对应文件上传记录
            int currentSize = Integer.valueOf(redisUtil.get(hashcode));//对应文件目前上传大小总和
            if(currentSize < fileTarSize){
                return currentSize;
            }else{
                new Exception("文件上传出错");
                return -1;
            }
        }else {
            redisUtil.set(hashcode, String.valueOf(0));
            return 0;
        }
    }

    /**
     * @author lxy
     * @date 2021-12-28 23:16
     * @param mf 分片
     * @param fileName 文件名
     * @param seq 分片序号
     * @param sliceSize 分片大小
     * @param hash 文件hash
     * @param fileTarSize 文件总大小
     * @return java.lang.String
     * @throws
     * @since
     */
    @Override
    public JSONObject uploadSlice(MultipartFile mf,String fileName,String seq,int sliceSize,String hash,int fileTarSize) throws IOException {
        String suffix = FileUtil.SUFFIX;
        String dirPath = FileUtil.CURRENTWORKDIR;
        File file = new File(dirPath+fileName+"."+seq+suffix);
        FileUtils.copyInputStreamToFile(mf.getInputStream(), file); //将MultipartFile转换为File

        JSONObject resJson = new JSONObject();
        String hashlock = hash + "lock";
        RLock lock1 = redisson.getLock(hashlock);
        try {
            lock1.lock();
            int currentSize = Integer.valueOf(redisUtil.get(hash));
            int mergeSize = currentSize + sliceSize;
            if (mergeSize >= fileTarSize) {
                System.out.println("开始合并");
                //合并成功后进行处理，添加文件到DB，删除redis中的hashcode，删除文件夹中的分片文件
                String mergeResult = mergeSlice(fileName, suffix);
                if (mergeResult.equals("merge_false")) {
                    resJson.put("shouldUpload","merge_false");
                    return resJson;
                }
                ExecutorService executor = Executors.newFixedThreadPool(1);
                Future<Boolean> res = executor.submit(new MergeCompletedProcess(hash, fileName, fileTarSize));
                resJson.put("shouldUpload",mergeResult);
                return resJson;
            } else {
                redisUtil.set(hash, String.valueOf(mergeSize));
                resJson.put("shouldUpload","upload_true");
                return resJson;
                //            List<Object> txResults = redisTemplate.execute(new SessionCallback<List<Object>>() {
                //                public List<Object> execute(RedisOperations operations) throws DataAccessException {
                //                    operations.watch(hash);
                //                    operations.multi();
                //                    operations.opsForValue().set(hash, String.valueOf(mergeSize));
                //                    List<Object> txResults = operations.exec();
                //                    return txResults;
                //                }
                //            });
                //            if(txResults.isEmpty()){
                //                return "upload_true";
                //            }
                //            return "upload_false";

            }
        }finally {
            lock1.unlock();
        }
    }

    /**
     * @author lxy
     * @date 2021-12-29 16:01
     * @param fileName 文件名
     * @param suffer 文件后缀
     * @return java.lang.String
     * @throws
     * @since
     */
    public static String mergeSlice(String fileName,String suffer) throws IOException {
        int promise = 0;
        List<File> partFiles = FileUtil.getDirFiles(FileUtil.CURRENTWORKDIR, suffer, fileName);
        Collections.sort(partFiles,(File o1,File o2)-> o1.getName().compareToIgnoreCase(o2.getName()));
        RandomAccessFile raf = null;
        try{
            raf = new RandomAccessFile(FileUtil.CURRENTWORKDIR+fileName, "rw");
            raf.setLength((partFiles.size()-1)*FileUtil.PARTFILESIZE+partFiles.get(partFiles.size() - 1).length());
        }finally {
            raf.close();
        }

        FileMergeHandlerExecutePool pool = new FileMergeHandlerExecutePool(partFiles.size());
        int partSize = partFiles.size();
        for (int i = 0; i < partSize; i++) {
            Future<Boolean> res = pool.submit(new MergeRunnable(i * FileUtil.PARTFILESIZE, fileName, partFiles.get(i)));
            try {
                if(res.get()){
                    promise++;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        System.out.println("合并结果，合并文件数："+partSize+",合并成功数："+promise);
        if(promise==partSize){
            return FileUtil.CURRENTWORKDIR+fileName;
        }else{
            return "merge_false";
        }
    }

    //String fileName,String seq,int sliceSize,String hash,int fileTarSize
    //合并成功后进行处理，添加文件到DB，删除redis中的hashcode，删除文件夹中的分片文件

    private class MergeCompletedProcess implements Callable<Boolean>{
        String hashcode;
        String fileName;
        int fileTarSize;

        public MergeCompletedProcess(String hashcode, String fileName, int fileTarSize) {
            this.hashcode = hashcode;
            this.fileName = fileName;
            this.fileTarSize = fileTarSize;
        }

        @Override
        public Boolean call(){
            boolean res = true;
            try{
                int setDBResult = FileOperationServiceImpl.this.fileOperationDao.setNewFile(hashcode, fileName, fileTarSize, FileUtil.CURRENTWORKDIR);
                if (setDBResult == 1){
                    System.out.println("插入数据库成功："+hashcode);
                }else {
                    System.out.println("插入数据库失败："+hashcode);
                    res = false;
                }
            }catch (Exception e){
                System.out.println("插入数据库异常："+hashcode);
                res = false;
                e.printStackTrace();
            }
            try {
                Boolean delCacheResult = FileOperationServiceImpl.this.redisUtil.del(hashcode);
                if(delCacheResult){
                    System.out.println("删除Redis键成功："+hashcode);
                }else {
                    System.out.println("删除Redis失败："+hashcode);
                    res = false;
                }
            }catch (Exception e){
                System.out.println("删除Redis异常："+hashcode);
                res = false;
                e.printStackTrace();
            }

            try{
                List<File> partFiles = FileUtil.getDirFiles(FileUtil.CURRENTWORKDIR, FileUtil.SUFFIX, fileName);
                List<String> delFilesResult = new ArrayList<>(partFiles.size());
                for(File f: partFiles){
                    boolean delete = f.delete();
                    if(!delete){
                        delFilesResult.add(f.getName());
                    }
                }
                if(delFilesResult.isEmpty()){
                    System.out.println("删除分片键成功："+hashcode);
                }else{
                    System.out.println("删除分片键失败："+Arrays.toString(delFilesResult.toArray()));
                    res = false;
                }
            }catch (Exception e){
                System.out.println("删除分片异常："+hashcode);
                res = false;
                e.printStackTrace();
            }
            return res;
        }
    }

    private static class MergeRunnable implements Callable<Boolean> {
        long startPos;
        String mergeFileName;
        File partFile;

        public MergeRunnable(long startPos, String mergeFileName, File partFile) {
            this.startPos = startPos;
            this.mergeFileName = mergeFileName;
            this.partFile = partFile;
        }

        @Override
        public Boolean call() throws Exception {
            RandomAccessFile rFile = null;
            FileInputStream fs = null;
            try {
                rFile = new RandomAccessFile(FileUtil.CURRENTWORKDIR+mergeFileName, "rw");
                rFile.seek(startPos);
                fs = new FileInputStream(partFile);
                byte[] bytes = new byte[fs.available()];
                fs.read(bytes);
                rFile.write(bytes);
            } catch (IOException e) {
                e.printStackTrace();
                return false;
            }finally {
                fs.close();
                rFile.close();

            }
            return true;
        }
    }


    @Override
    public String getFileLocal(String hashcode) {
        return fileOperationDao.getFileLocal(hashcode);
    }

    @Override
    public int checkFileExistDB(String hashcode) {
        return fileOperationDao.checkFileExistDB(hashcode);
    }


}
